package fr.babos.graphic.components.containers.lists 
{
	import com.greensock.easing.Quad;
	import com.greensock.easing.Strong;
	import com.greensock.TweenMax;
	import fr.babos.graphic.components.base.ResizableComponent;
	import fr.babos.touch.events.GestureTouchDragEvent;
	import fr.babos.touch.events.GestureTouchEvent;
	import org.osflash.signals.Signal;
	
	/**
	 * @author ZoulouX
	 */
	public class StepVirtualList extends AVirtualList
	{
		/**
		 * La tween en cours
		 */
		protected var _currentScrollTween					:TweenMax;
		
		/**
		 * La durée de l'animation
		 */
		protected var _animationDuration					:Number					= .6;
		
		/**
		 * Le easing de l'animation
		 */
		protected var _animationEase						:Function				= Quad.easeOut
		
		/**
		 * Le taux de modification de durée de l'animation par rapport à la vélocity (0 pour ne pas prendre en compte la vélocité)
		 */
		protected var _velocityAnimationBreak				:Number					= .005;
		
		/**
		 * La vélocité minimum pour changer de cran sans être à la moitié du déplacement
		 */
		protected var _velocityStepLimit					:int					= 2;
		
		/**
		 * L'index affiché
		 */
		protected var _selectedIndex						:int					= 0;
		
		/**
		 * Lorsque l'index change
		 */
		protected var _onIndexChange						:Signal					= new Signal();
		
		
		
		/**
		 * La durée de l'animation
		 */
		public function get animationDuration ():Number { return _animationDuration; }
		public function set animationDuration (value:Number):void
		{
			_animationDuration = value;
		}
		
		/**
		 * Le easing de l'animation
		 */
		public function get animationEase ():Function { return _animationEase; }
		public function set animationEase (value:Function):void
		{
			_animationEase = value;
		}
		
		/**
		 * Le taux de modification de durée de l'animation par rapport à la vélocity (0 pour ne pas prendre en compte la vélocité)
		 */
		public function get velocityAnimationBreak ():Number { return _velocityAnimationBreak; }
		public function set velocityAnimationBreak (value:Number):void
		{
			_velocityAnimationBreak = value;
		}
		
		/**
		 * La vélocité minimum pour changer de cran sans être à la moitié du déplacement
		 */
		public function get velocityStepLimit ():int { return _velocityStepLimit; }
		public function set velocityStepLimit (value:int):void
		{
			_velocityStepLimit = value;
		}
		
		/**
		 * L'index affiché
		 */
		public function get selectedIndex ():int { return _selectedIndex; }
		public function set selectedIndex (value:int):void
		{
			_selectedIndex = value;
			
			_velocity = 0;
			
			replaceList(true);
		}
		
		/**
		 * Lorsque l'index change
		 */
		public function get onIndexChange ():Signal { return _onIndexChange; }
		public function set onIndexChange (value:Signal):void
		{
			_onIndexChange = value;
		}
		
		
		/**
		 * Le constructeur
		 * @param	pDelegate : Le delegate de cette liste qui va fournir les éléments et les informations sur le nombre d'éléments
		 * @param	pOrientation : L'orientation de la liste (voir statiques)
		 * @param	pBack : Si un fond transparent est généré automatiquement (permet d'intercepter tous les touchEvents)
		 */
		public function StepVirtualList (pDelegate:IVirtualListDelegate, pOrientation:String = "vertical", pBack:Boolean = false)
		{
			// Relayer la construction
			super(pDelegate, pOrientation, pBack);
		}
		
		/**
		 * Redimensionnement du composant
		 */
		override protected function resized ():void
		{
			// Replacer le fond
			if (_back != null)
			{
				_back.width = _localWidth;
				_back.height = _localHeight;
			}
			
			// Replacer
			replaceList(true);
			
			// La liste a bougé
			listMovedHandler(false);
		}
		
		/**
		 * Les évènements touch
		 * @param	event
		 */
		override public function gestureTouchHandler (event:GestureTouchEvent):void
		{
			// Si on est sur un event drag
			if (event is GestureTouchDragEvent)
			{
				// Récupérer l'event drag
				var dragEvent:GestureTouchDragEvent = event as GestureTouchDragEvent;
				
				// Un point vient dérouiller le déplacement (on a pas encore de direction)
				if (event.type == GestureTouchDragEvent.LOCK_DRAG)
				{
					// On stoppe la tween
					if (_currentScrollTween != null)
					{
						_currentScrollTween.kill();
						_currentScrollTween = null;
					}
					
					// Le drag est vérouille
					_dragLocked = true;
					
					// La liste n'a pas bougé
					_moved = 0;
				}
				
				// C'est dévérouillé
				else if (event.type == GestureTouchDragEvent.UNLOCK_DRAG)
				{
					// Le drag est dévérouillé
					_dragLocked = false;
					
					// Lui appliquer une limite en cas de dépassement
					replaceList();
					
					// Plus de vélocité
					_velocity = 0;
				}
				
				// Vérifier si on est dans la bonne direction pour le drag
				else if (
						event.type == GestureTouchDragEvent.DRAGGING
						&&
						(
							dragEvent[_dragDirection]
							||
							(dragEvent.isBoth && _dragAllowUnknownDirection)
							||
							(dragEvent[_oppositeDragDirection] && _dragAllowOppositeDirection)
						)
					)
				{
					
					// La liste a bougé
					_moved = 0;
					
					// Récupérer la vélocité du point
					_velocity = dragEvent[_deltaTouchVar];
					
					// Gestion de la vélocité
					replaceList(false);
					
					// On applique cette vélocité au scroll
					currentScroll += _velocity;
				}
			}
		}
		
		
		/**
		 * La liste dépasse, replacer la liste
		 * @param	pImmediate : Replacement immediat
		 */
		override protected function replaceList (pImmediate:Boolean = false):void
		{
			// Vérifier que notre container ai une largeur
			if (_container[_contentSizeVar] > 0)
			{
				// On stoppe la tween
				if (_currentScrollTween != null)
				{
					_currentScrollTween.kill();
					_currentScrollTween = null;
				}
				
				// Calculer le décallage par rapport à la moitié
				var offset:Number = _container[_contentSizeVar] / 2 - _typicalElementSize / 2;
				
				// La destination
				var destination:Number;
				
				// La durée de l'animation
				var duration:Number = pImmediate ? 0 : _animationDuration;
				
				// Si on n'est pas en drag
				if (!_dragLocked)
				{
					// Si on est en immediat
					if (pImmediate)
					{
						// On calle par rapport à l'index
						destination = - _selectedIndex * _typicalElementSize + offset;
					}
					else
					{
						// Le décallage par la vélocité
						var velocityDecay:Number = 0;
						
						// Si on a une vélocité, ajouter ou soustraire un cran
						if (_velocity > _velocityStepLimit)
						{
							velocityDecay = _typicalElementSize / 1.9;
						}
						else if (_velocity < - _velocityStepLimit)
						{
							velocityDecay = - _typicalElementSize / 1.9;
						}
						
						// Calculer la position la plus proche
						destination = Math.round((_container[_offsetVar] - offset + (velocityDecay)) / _typicalElementSize) * _typicalElementSize + offset;
					}
					
					// Limiter aux positions min et max
					destination = Math.max(
						- _lastElementIndex * _typicalElementSize + offset,
						Math.min(
							destination,
							- _firstElementIndex * _typicalElementSize + offset
						)
					);
					
					// Récupérer le nouvel index
					var newIndex:int = ( - destination + offset) / _typicalElementSize;
					
					// Si l'index est différent
					if (_selectedIndex != newIndex)
					{
						// Enregistrer le nouvel index
						_selectedIndex = newIndex;
						
						// Signaler
						_onIndexChange.dispatch();
					}
					
					// Limiter la vitesse selon la vélocité
					if (
							(destination < currentScroll && _velocity < 0)
							||
							(destination > currentScroll && _velocity > 0)
						)
					{
						duration = (duration * (1 - Math.abs(_velocity) * _velocityAnimationBreak))
					}
					
					// Appliquer l'animation
					_currentScrollTween = TweenMax.to(this, duration, {
						ease: _animationEase,
						currentScroll: destination
					});
				}
				
				// Vérifier que ça dépasse
				else if (
							_container[_offsetVar] < - _lastElementIndex * _typicalElementSize + offset
							||
							_container[_offsetVar] > - _firstElementIndex * _typicalElementSize + offset
						)
				{
					// Limiter la vélocité
					_velocity = _velocity / _velocityOutBreak;
				}
			}
		}
		
		/**
		 * Récupérer des informations sur la position
		 */
		override public function getPositionInformations ():Array
		{
			var positionInformations:Array;
			
			if (_delegate != null)
			{
				positionInformations = [_selectedIndex];
			}
			
			return positionInformations;
		}
		
		/**
		 * Définir la position
		 */
		override public function setPositionInformations (pPositionInformations:Array):void
		{
			if (_delegate != null && pPositionInformations != null && pPositionInformations.length == 1)
			{
				selectedIndex = pPositionInformations[0];
			}
		}
		
		/**
		 * Destruction
		 */
		override public function dispose():void 
		{
			_onIndexChange.removeAll();
			super.dispose();
		}
		
		override public function stop ():void
		{
			TweenMax.killTweensOf(this);
		}
	}
}